package roborally.model;

import exception.IllegalPositionException;
import be.kuleuven.cs.som.annotate.*;
import be.kuleuven.cs.som.taglet.*;

/**
 * A Class of positions representing a set of x-y coordinates. Positions' coordinates have a range of [0; Long.MAX_VALUE].
 * It is possible to find the manhattan distance between two positions, and a positions' neighbor given a specific orientation.
 * 
 * @author Mathieu REYMOND
 * @author Joren VERSPEURT
 * 
 * @version 2.0
 *
 *@invar ...
 *		 | isValidCoordinates(getX(), getY(), getMaxX(), getMaxY())
 */
@Value
public class Position {
	
	/**
	 * Create a new position given specific x and y coordinates.
	 * The default max values of the different coordinates are Long.MAX_VALUE
	 * 
	 * @param x The given x coordinate
	 * @param y The given y coordinate
	 * 
	 * @throws IllegalPositionException
	 * 
	 * @effect ...
	 * 		   | this(x, y, Long.MAX_VALUE, Long.MAX_VALUE)
	 */
	public Position(long x, long y) throws IllegalPositionException {
		
		this(x, y, Long.MAX_VALUE, Long.MAX_VALUE);
	}
	
	/**
	 * Create a new position given specific x, y, maxX and maxY coordinates. 
	 * 
	 * @param x The given x coordinate
	 * @param y The given y coordinate
	 * @param maxX The given max x coordinate
	 * @param maxY The given max y coordinate
	 * 
	 * @throws IllegalPositionException
	 * 		   | !Position.isValidCoordinates(x, y, maxX, maxY)
	 * 
	 * @post ...
	 * 		 | x == new.getX()
	 * @post ...
	 * 		 | y == new.getY()
	 * @post ...
	 * 		 | maxX == new.getMaxX()
	 * @post ...
	 * 		 | maxY == new.getMaxY()
	 * 
	 * @throws IllegalPositionException
	 * 		   | !Position.isValidCoordinates(x, y, maxX, maxY)
	 */
	public Position(long x, long y, long maxX, long maxY) throws IllegalPositionException{
		if(!Position.isValidCoordinates(x, y, maxX, maxY)) throw new IllegalPositionException("Could not create a position with coordinates (" + x + ", " + y + " ) and Max coordinates (" +
				maxX + ", "+ maxY + " ).");
		
		this.x = x;
		this.y = y;
		this.maxX = maxX;
		this.maxY = maxY;
		
	}
	
	/**
	 * Check if the given coordinates are valid.
	 * 
	 * @param x The x coordinate to check
	 * @param y The y coordinate to check
	 * @param maxX The max x coordinate to check
	 * @param maxY The max y coordinate to check
	 * 
	 * @return ...
	 * 		   | x >= 0 && y >= 0 && x <= maxX && y <= maxY
	 */
	public static boolean isValidCoordinates(long x, long y, long maxX, long maxY) {
		
		return x >= 0 && y >= 0 && x <= maxX && y <= maxY;
	}
	
	/**
	 * Get the manhattan distance between this position and another given position.
	 * 
	 * @param other The other given position
	 * 
	 * @return ...
	 * 		   | Math.abs(other.getX() - getX()) + Math.abs(other.getY() - getY())
	 */
	public long getManhattanDistance(Position other) {
		
		//because other has been correctly created and is immutable, it has to be valid
		return Math.abs(other.getX() - getX()) + Math.abs(other.getY() - getY());
	}
	
	/**
	 * Get The position's neighbor in the given orientation.
	 * 
	 * @param orientation The given orientation
	 * 
	 * @return ...
	 * 		   | result == this(this.getX()+orientation.getRelativeCoordinatesX(), this.getY()+ orientation.getRelativeCoordinatesY())
	 * 
	 * @throws IllegalPositionException
	 */
	public Position getNeighbour(Orientation orientation) throws IllegalPositionException {
		
		return new Position(this.getX()+orientation.getRelativeCoordinatesX(), this.getY()+ orientation.getRelativeCoordinatesY());
	}
	
	/**
	 * Get The x coordinate of this position
	 */
	@Basic @Immutable
	public final long getX() {
		
		return x;
	}
	
	/**
	 * The positions' x coordinate
	 */
	private final long x;

	/**
	 * Get The y coordinate of this position
	 */
	@Basic @Immutable
	public final long getY() {
		
		return y;
	}
	
	/**
	 * The positions' y coordinate
	 */
	private final long y;

	/**
	 * Get The maximum x coordinate of this position
	 */
	@Basic @Immutable
	public final long getMaxX() {
		
		return maxX;
	}
	
	/**
	 * The positions' maximum x coordinate
	 */
	private final long maxX;

	/**
	 * Get The maximum y coordinate of this position
	 */
	@Basic @Immutable
	public final long getMaxY() {
		
		return maxY;
	}
	
	/**
	 * The positions' maximum y coordinate
	 */
	private final long maxY;

	/**
	 * Checks if the given position is placed along the line defined by this position and the given orientation.
	 * 
	 * @param other The given position
	 * @param orientation The given orientation
	 * 
	 * @return ...
	 * 		   | result == Math.abs(other.getX() - getX()) >= Math.abs(other.getX() - (getX() + orientation.getRelativeCoordinatesX())) && Math.abs(other.getY() - getY()) >= Math.abs(other.getY() - (getY() + orientation.getRelativeCoordinatesY()))
	 */
	public boolean isPlacedAtOrientationOf(Position other, Orientation orientation) {
	
		return ( Math.abs(other.getX() - getX()) > Math.abs(other.getX() - (getX() + orientation.getRelativeCoordinatesX())) ||
				Math.abs(other.getX() - getX()) == 0 )&&
				( Math.abs(other.getY() - getY()) > Math.abs(other.getY() - (getY() + orientation.getRelativeCoordinatesY())) || 
				Math.abs(other.getY() - getY()) == 0 )&& (other.getX() != this.getX() || other.getY() != this.getY());
	}

	/**
	 * Gives the hashcode of this position.
	 * 
	 * @return ...
	 * 		   | result == Double.valueOf(getX()%(Math.sqrt(Integer.MAX_VALUE))+getY()%(Math.sqrt(Integer.MAX_VALUE))*Math.sqrt(Integer.MAX_VALUE)).intValue();
	 */
	public int hashCode() {
		
		return Double.valueOf(getX()%(Math.sqrt(Integer.MAX_VALUE))+getY()%(Math.sqrt(Integer.MAX_VALUE))*Math.sqrt(Integer.MAX_VALUE)).intValue();
	}
	
	/**
	 * Check if this position equals a given object.
	 * 
	 * @param obj The object to check
	 * 
	 * @return ...
	 * 		   | if(obj instanceof Position)
	 * 		   | then result == ((Position) obj).getX() == x && ((Position) obj).getY() == y
	 * 		   | else result == false
	 */
	public boolean equals(java.lang.Object obj) {
		
		boolean result = false;
		
		if(obj instanceof Position) { //if obj is not instance of Position, then it's not equal to this position
			result = ((Position) obj).getX() == x && ((Position) obj).getY() == y;
		}
		
		return result;
	}

//	/**
//	 * Clone this position.
//	 * 
//	 * @return ...
//	 * 		   | result == this(getX(), getY(), getMaxX(), getMaxY())
//	 */
//	public Position clone() {
//		
//		return new Position(getX(), getY(), getMaxX(), getMaxY());
//	}
	
	/**
	 * Get a string that describes this position
	 * 
	 * @return ...
	 * 		   | result == "(" + getX() + "," + getY() + ")"
	 */
	public String toString() {
		
		return "(" + getX() + "," + getY() + ")";
	}

}
